package samples

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import org.junit.Test
import java.util.concurrent.CancellationException

/**
 *
 * @author mph
 * @date 2025/1/8
 */
@OptIn(ExperimentalCoroutinesApi::class)
class Cancel_Timeout_Test {

    @Test
    fun testCancellable() = runTest {
//        cancelInSameThread()
//        cancelInOtherThreadByIsActive()
        cancelInOtherThreadByFinally()
    }

    private suspend fun cancelInSameThread() = coroutineScope {
        val startTime = System.currentTimeMillis()
        //注意这里的launch没有指定新的Dispatcher，因此是在当前线程中执行
        val job = launch {
            var nextPrintTime = startTime
            var i = 0
            repeat(1000) { // cancellable computation loop
                // print a message twice a second
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping ${i++} ...")
                    nextPrintTime += 500L
                }
            }
        }
        delay(1300L) // delay a bit
        println("main: I'm tired of waiting!")
        job.cancelAndJoin() // cancels the job and waits for its completion
        println("main: Now I can quit.")
    }

    //通过内部isActive扩展变量判断是否继续执行
    private suspend fun cancelInOtherThreadByIsActive() = coroutineScope {
        //这里是在Dispatchers.Default中启动的，因此会启动一个新的线程
        val job = launch(Dispatchers.Default) {
            var i = 0
            while (isActive) { // cancellable computation loop
                // print a message twice a second
                println("job: I'm sleeping ${i++} ...")
                delay(500L)
            }
        }
        delay(1300L) // delay a bit
        println("main: I'm tired of waiting!")
        job.cancelAndJoin() // cancels the job and waits for its completion
        println("main: Now I can quit.")
    }

    /**
     * 通过try-finally终止
     * 因为coroutine的cancel会抛出CancellationException，所以可以用try-finally来终止
     */
    suspend fun cancelInOtherThreadByFinally() = coroutineScope {
        //这里是在Dispatchers.Default中启动的，因此会启动一个新的线程
        val job = launch(Dispatchers.Default) {
            var i = 0
            try {
                repeat(1000) { // cancellable computation loop
                    // print a message twice a second
                    println("job: I'm sleeping ${i++} ...")
                    delay(500L)
                }
            } catch (e: CancellationException) {
                //这里可以打印信息看看
                log(e.message)
            } finally {
                //在finally中调用任何一个suspend函数都会抛出异常
                launch {
                    println("job: I'm running finally") //这里不会输出
                }
                //可以如下方式调用suspend函数
                withContext(NonCancellable) {
                    println("job: I'm running finally") //这里可以输出
                }
            }
        }
        delay(1300L) // delay a bit
        println("main: I'm tired of waiting!")
        job.cancelAndJoin() // cancels the job and waits for its completion
        println("main: Now I can quit.")
    }

    @Test
    fun testTimeout() = runTest {
        //会抛出异常
//        withTimeout(3000) {
//            repeat(1000) {
//                println("job $it")
//                delay(500L)
//            }
//        }
        //不会抛出异常，但是会返回null
        val result = withTimeoutOrNull(3000) {
            repeat(1000) {
                println("job $it")
                delay(500L)
            }
            "Finished Ok!"
        }
        println("Result is $result")
    }

    /**
     * withTimeout抛出异常的动作和其内部代码块之间是异步关系，因此无法预测抛出异常的时机，所以如果你在其代码块内部打开了某个资源，一定记得要在外面close
     */
    @Test
    fun testTimeoutInCloseResource() {
        runBlocking {
            repeat(10_000) { // Launch 10K coroutines
                launch {
                    var resource: Resource? = null
                    try {
                        //500和499这组数字不是固定的，只是在当前机器上，刚好能模拟出Resource刚创建完就超时的情况
                        /*val resource = withTimeout(500) { // Timeout of 60 ms
                            delay(499) // Delay for 50 ms
                            Resource() // Acquire a resource and return it from withTimeout block
                        }
                        //超时后抛除了异常，这句就不再会执行到
                        resource.close() // Release the resource
                        */
                        withTimeout(500) { // Timeout of 60 ms
                            delay(499) // Delay for 50 ms
                            //尽量不要使用withTimeout返回值作为引用，因为不一定执行到从而在调用close方法时造成空指针异常
                            resource = Resource() // Acquire a resource and return it from withTimeout block
                        }
                    } catch (e: Exception) {
                        println(e.message)
                    } finally {
                        //改成这样就不会泄漏了
                        resource?.close()
                    }
                }
            }
        }
        // Outside of runBlocking all coroutines have completed
        println(acquired) // Print the number of resources still acquired
    }

}

var acquired = 0

class Resource {
    init {
        acquired++
    } // Acquire the resource

    fun close() {
        acquired--
    } // Release the resource
}